package com.naiterui.ehp.bp.utils.lang;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.im4java.core.ConvertCmd;
import org.im4java.core.GMOperation;
import org.im4java.core.IM4JavaException;
import org.im4java.process.Pipe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

/**
 * <pre>
 *   图片工具类，可以支持压缩
 *   文件压缩需要在服务器上安装GraphicsMagick软件，推荐使用最新版本
 * </pre>
 *
 * @author 王泽浩
 * @date 2018/5/28 下午2:55
 * @since 1.0.0
 */
@SuppressWarnings({"unused", "WeakerAccess"})
public class ImageUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ImageUtil.class);
    /**
     * 图片配置列表
     */
    private static final Map<Integer, ImageProperties> IMAGE_PROPERTIES_MAP = ImageUtil.getImageProperties();

    /**
     * <pre>
     *      根据路径path上传图片，文件名使用当前时间戳.
     * </pre>
     *
     * @param path          图片路径
     * @param multipartFile 文件流
     * @return 文件上传路径
     */
    public static String upload(String path, MultipartFile multipartFile) throws FileUploadException {
        try {
            return ImageUtil.uploadByPath(path, multipartFile.getInputStream());
        } catch (IOException e) {
            throw new FileUploadException("文件上传失败", e);
        }
    }

    private static String uploadByPath(String abPath, InputStream inputStream) throws FileUploadException {
        // 文件路径+文件名
        String path = abPath;

        // 判断文件夹是否存在，如果不存在，那么创建，因为GM压缩如果文件夹不存在，不会创建
        File file = new File(path);
        if (!file.getParentFile().exists()) {
            // 如果路径不存在,则创建
            boolean mkdirs = file.getParentFile().mkdirs();
            if (!mkdirs) {
                throw new FileUploadException(String.format("文件目录创建失败 %s", path));
            }
        }
        // 文件路径+文件名
        try {
            /*if (file.exists()) {
                throw new FileUploadException(String.format("文件已存在 %s", path));
            }*/
            FileUtils.copyInputStreamToFile(inputStream, file);
        } catch (IOException e) {
            LOGGER.error("文件上传失败", e);
            throw new FileUploadException(String.format("文件上传失败 %s", path), e);
        }
        return path;
    }

    /**
     * <pre>
     *      根据业务类型上传图片，文件名使用当前时间戳.
     *      根据配置的业务类型，获取对应上传路径，保存图片。 如果配置的path中存在需要替换的数据，请使用 %s 来代替,并且传入对应的参数列表
     * </pre>
     *
     * @param type          业务图片类型
     * @param multipartFile 文件流
     * @param args          路径拼装参数
     * @return 文件上传路径
     */
    public static String upload(Integer type, MultipartFile multipartFile, Object... args) throws FileUploadException {
        String fileName = System.currentTimeMillis() + ".jpg";
        try {
            return ImageUtil.upload(type, multipartFile.getInputStream(), fileName, args);
        } catch (IOException e) {
            throw new FileUploadException("文件上传失败", e);
        }
    }

    /**
     * <pre>
     *      根据业务类型上传图片，不制定文件名
     *      根据配置的业务类型，获取对应上传路径，保存图片。 如果配置的path中存在需要替换的数据，请使用 %s 来代替,并且传入对应的参数列表
     * </pre>
     *
     * @param type        业务图片类型
     * @param inputStream 输入流
     * @param fileName    文件名
     * @param args        路径拼装参数
     * @return 文件上传路径
     */
    public static String upload(Integer type, InputStream inputStream, String fileName, Object... args) throws FileUploadException {
        // 文件路径+文件名
        String pathName = ImageUtil.getPathName(type, fileName, args);
        File destination = new File(pathName);
        try {
            if (destination.exists()) {
                throw new FileUploadException(String.format("文件已存在 %s", pathName));
            }
            FileUtils.copyInputStreamToFile(inputStream, destination);
        } catch (IOException e) {
            LOGGER.error("文件上传失败", e);
            throw new FileUploadException(String.format("文件上传失败 %s", pathName), e);
        }
        return pathName;
    }

    /**
     * <pre>
     *     根据图片类型和图片名称获取文件上传路径，如果上传路径不存在，那么需要创建文件路径
     * </pre>
     *
     * @param type     业务图片类型
     * @param fileName 文件名
     * @param args     路径拼装参数
     * @return 文件绝对路径
     * @throws FileUploadException 文件上传异常
     */
    private static String getPathName(Integer type, String fileName, Object... args) throws FileUploadException {
        ImageProperties imageProperties = IMAGE_PROPERTIES_MAP.get(type);
        if (imageProperties == null) {
            throw new FileUploadException(String.format("当前type配置不存在 %s", type));
        }
        // 文件路径
        String imagePropertiesPath = imageProperties.getPath();
        String path = String.format(imagePropertiesPath, args);

        // 判断文件夹是否存在，如果不存在，那么创建，因为GM压缩如果文件夹不存在，不会创建
        File file = new File(path);
        if (!file.exists()) {
            // 如果路径不存在,则创建
            boolean mkdirs = file.mkdirs();
            if (!mkdirs) {
                throw new FileUploadException(String.format("文件目录创建失败 %s", path));
            }
        }
        // 文件路径+文件名
        return path.endsWith("/") ? path + fileName : path + "/" + fileName;
    }

    /**
     * <pre>
     *      根据业务类型上传图片，不制定文件名
     *      根据配置的业务类型，获取对应上传路径，保存图片。 如果配置的path中存在需要替换的数据，请使用 %s 来代替,并且传入对应的参数列表
     * </pre>
     *
     * @param type        业务图片类型
     * @param inputStream 输入流
     * @param args        路径拼装参数
     * @return 文件上传路径
     * @throws FileUploadException 文件上传异常
     */
    public static String compression(Integer type, InputStream inputStream, Object... args) throws FileUploadException {
        String fileName = System.currentTimeMillis() + ".jpg";
        return ImageUtil.compression(type, inputStream, fileName, args);
    }

    /**
     * <pre>
     *      根据业务类型上传图片，不制定文件名
     *      根据配置的业务类型，获取对应上传路径，保存图片。 如果配置的path中存在需要替换的数据，请使用 %s 来代替,并且传入对应的参数列表
     * </pre>
     *
     * @param type        业务图片类型
     * @param inputStream 输入流
     * @param fileName    文件名
     * @param args        路径拼装参数
     * @return 文件上传路径
     * @throws FileUploadException 文件上传异常
     */
    public static String compression(Integer type, InputStream inputStream, String fileName, Object... args) throws FileUploadException {
        String pathName = ImageUtil.getPathName(type, fileName, args);
        ImageUtil.compress(inputStream, pathName);
        return pathName;
    }

    /**
     * <pre>
     *      根据业务类型上传图片，不制定文件名
     *      根据配置的业务类型，获取对应上传路径，保存图片。 如果配置的path中存在需要替换的数据，请使用 %s 来代替,并且传入对应的参数列表
     * </pre>
     *
     * @param type        业务图片类型
     * @param inputStream 输入流
     * @param args        路径拼装参数
     * @return 文件上传路径, 包含原始路径和压缩路径
     * @throws FileUploadException 文件上传异常
     */
    public static Path compressionWithOriginal(Integer type, InputStream inputStream, Object... args) throws FileUploadException {
        String fileName = System.currentTimeMillis() + "";
        // 上传原始图
        String originalPath = ImageUtil.upload(type, inputStream, fileName + "_original.jpg", args);
        // 压缩图
        String compressPath = ImageUtil.getPathName(type, fileName + ".jpg", args);
        ImageUtil.compress(inputStream, compressPath);
        return new Path(originalPath, compressPath);
    }

    /**
     * <pre>
     * 获取文件在url上访问的URI
     * </pre>
     *
     * @param path 文件绝对路径
     * @return 在URL上访问的URI
     */
    public static String getUri(String path, Integer type) {
        Assert.notNull(path, "文件路径不能为null");
        ImageProperties imageProperties = IMAGE_PROPERTIES_MAP.get(type);
        String basePath = imageProperties.getBasePath();
        return basePath.endsWith("/") ? path.replace(basePath, "/") : path.replace(basePath + "/", "/");
    }

    /**
     * <pre>
     * 根据输入流压缩图片
     * </pre>
     *
     * @param inputStream 输入流
     * @param pathName    图片保存目标路径
     */
    public static void compress(InputStream inputStream, String pathName) throws FileUploadException {
        LOGGER.debug("压缩图片 请求参数：pathName = {}", pathName);
        // 输入流不能为nul
        assert inputStream != null;
        Pipe pipeIn = new Pipe(inputStream, null);
        GMOperation operation = new GMOperation();
        operation.addImage("-");
        operation.addRawArgs("-resize", "640x480>");
        operation.quality(80D);
        operation.addImage(pathName);
        ConvertCmd convertCmd = new ConvertCmd(true);
        convertCmd.setInputProvider(pipeIn);
        convertCmd.setAsyncMode(true);
        try {
            convertCmd.run(operation);
        } catch (IOException | InterruptedException | IM4JavaException e) {
            throw new FileUploadException(String.format("文件压缩失败 path:%s", pathName), e);
        }
    }

    public static void compress(String inputPath, String outputPath, String size) throws FileUploadException {
        LOGGER.info("文件压缩 请求参数：inputPath = {}, outputPath = {}, size = {}", inputPath, outputPath, size);

        // 输入流不能为nul
        GMOperation operation = new GMOperation();
        operation.addImage(inputPath);
        operation.addRawArgs("-resize", size);
        operation.quality(80D);
        operation.addImage(outputPath);
        ConvertCmd convertCmd = new ConvertCmd(true);
        convertCmd.setAsyncMode(false);
        try {
            convertCmd.run(operation);
        } catch (IOException | InterruptedException | IM4JavaException e) {
            throw new FileUploadException(String.format("文件压缩失败 inputPath:%s outputPath:%s size:%s", inputPath, outputPath, size), e);
        }
    }

    /**
     * 获取图片配置 key 为type value为type对应的图片配置
     *
     * @return 图片配置列表
     */
    private static Map<Integer, ImageProperties> getImageProperties() {
        ImagePropertiess ehp = ConfigUtil.bindProperties("naiterui", ImagePropertiess.class);
        if (ehp == null) {
            return new HashMap<>(0);
        }
        return ehp.getImage().stream().collect(Collectors.toMap(ImageProperties::getType, e -> e));
    }

    /**
     * 获取文件基础上传路径
     *
     * @return 文件基础上传路径
     */
    public static String getBaseUpload(Integer type) {
        ImageProperties imageProperties = IMAGE_PROPERTIES_MAP.get(type);
        if (imageProperties == null) {
            return "";
        }
        return imageProperties.getBasePath();
    }

    public static class Path {

        /**
         * 原图路径
         */
        private String originalPath;
        /**
         * 压缩路径
         */
        private String compressPath;

        public Path(String originalPath, String compressPath) {
            this.originalPath = originalPath;
            this.compressPath = compressPath;
        }

        public String getOriginalPath() {
            return this.originalPath;
        }

        public void setOriginalPath(String originalPath) {
            this.originalPath = originalPath;
        }

        public String getCompressPath() {
            return this.compressPath;
        }

        public void setCompressPath(String compressPath) {
            this.compressPath = compressPath;
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this);
        }
    }

    @ConfigurationProperties(value = "naiterui")
    public static class ImagePropertiess {

        /**
         * 图片配置列表
         */
        private List<ImageProperties> image = new ArrayList<>();

        public List<ImageProperties> getImage() {
            return this.image;
        }

        public void setImage(List<ImageProperties> image) {
            this.image = image;
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this);
        }

    }

    public static class ImageProperties {

        /**
         * 图片类型
         */
        private Integer type;
        /**
         * 类型名称
         */
        private String name;
        /**
         * 图片上传截取路径
         */
        private String basePath;
        /**
         * 图片上传图路径
         */
        private String path;

        public Integer getType() {
            return this.type;
        }

        public void setType(Integer type) {
            this.type = type;
        }

        public String getName() {
            return this.name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getPath() {
            return this.path;
        }

        public void setPath(String path) {
            this.path = path;
        }

        public String getBasePath() {
            return this.basePath;
        }

        public void setBasePath(String basePath) {
            this.basePath = basePath;
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this);
        }
    }

}
